package learm.pers.studygitlab.code.controller;

import lombok.SneakyThrows;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.net.URLEncoder;
import java.util.*;

/**
 * @ClassName: OauthController
 * @Description:
 * @Author: fangqing
 * @Date: 2021/7/15
 */
@Controller
@RequestMapping(value="/oauth")
public class OauthController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Value("${oauth2.server.url:https://gitlab.com}")
    private String gitlabServerUrl;
    @Value("${oauth2.client.id:7c145011b7b35102399d7e5fc8103e77b4002461a2933f172d884d93c4fd062d}")
    private String clientId;
    @Value("${oauth2.client.secret:60dd275787180f4f3e44caf5301879a2b0224ab7666ce64942090c675c78eb41}")
    private String clientSecret;
    @Value("${oauth2.client.callback.url:http://localhost:8080/devsecops/callback}")
    private String callbackUrl;

    @Value("${gitlab.url}")
    private String GITLAB_URL;


    private static final String CURRENT_USER = "CurrentUser";
    private static final String AUTHORIZATION_KEY = "Authorization";
    private Map<String, User> userStore = new HashMap<>();
    private RestTemplate restTemplate = new RestTemplate();


    @SneakyThrows
    @GetMapping(value="/gitlab")
    public void getGitlabRedirectUri(HttpServletResponse response1) {
        String indexUrl = "";
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(GITLAB_URL + "/users/sign_in");
        HttpResponse response = null;
        try {
            response = httpClient.execute(httpGet);
            String cookie = null;
            String authenticityToken = null;
            if(response.getStatusLine().getStatusCode() == 200){
                Header[] allHeaders = response.getAllHeaders();
                for (int i = 0; i < allHeaders.length; i++) {
                    if(allHeaders[i].toString().contains("Set-Cookie:")){
                        cookie = StringUtils.substringBetween(allHeaders[i].toString(), "_gitlab_session=", ";");
                    }else{
                        continue;
                    }
                }
            }
            String line = EntityUtils.toString(response.getEntity());
            if (line.contains("name=\"csrf-token\"")) {
                authenticityToken = (StringUtils.substringBetween(line, "name=\"csrf-token\" content=\"", "\" />"));
            }
            System.out.println(authenticityToken);


            HttpPost post = new HttpPost(GITLAB_URL + "/users/sign_in");
            //添加头部信息
            post.addHeader("Cookie", "_gitlab_session=" + cookie);
            // 设置post参数
            List<NameValuePair> parameters = new ArrayList<NameValuePair>(0);
            parameters.add(new BasicNameValuePair("authenticity_token", authenticityToken));
            parameters.add(new BasicNameValuePair("user[login]", "root"));
            parameters.add(new BasicNameValuePair("user[password]", "seczone@123"));
            parameters.add(new BasicNameValuePair("user[remember_me]", "0"));
            // 构造一个form表单式的实体
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters);
            // 将请求实体设置到httpPost对象中
            post.setEntity(formEntity);
            HttpResponse httpResponse = httpClient.execute(post);

            if(httpResponse != null){
                Header[] allHeaders = httpResponse.getAllHeaders();
                for (int i = 0; i < allHeaders.length; i++) {
                    if(allHeaders[i].toString().contains("Location")){
                        indexUrl = allHeaders[i].toString().replace("Location:","");
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 释放资源
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(indexUrl != ""){
            response1.sendRedirect(indexUrl);
            //return "redirect:" + indexUrl;
        }
    }




    @GetMapping({"/main","/"})
    //@ResponseBody
    public String main(String url) {
        User user = (User) RequestContextHolder.getRequestAttributes().getAttribute(CURRENT_USER, RequestAttributes.SCOPE_SESSION);
        //return "<html><body>hi:" + user.username + " This is Main</body></html>";
        return "redirect:" + url;
    }

    /**
     * 授权后redirect url
     * @param code 用于获取accessToken，只能使用一次
     */
    @GetMapping("/callback")
    public String callback(@RequestParam(value = "code", required = false) String code,
                           RedirectAttributes redirectAttributes,
                           HttpServletRequest request, HttpServletResponse response) {
        String referer = request.getParameter("referer");
        String accessToken = getAccessToken(code, buildCallbackUrl(referer));
        User user = getUser(accessToken);

        String uuid = UUID.randomUUID().toString();
        userStore.put(uuid, user);
        //set cookie
        response.addCookie(new Cookie(AUTHORIZATION_KEY, uuid));
        return "redirect:" + referer;
    }

    private String buildCallbackUrl(String referer) {
        return callbackUrl + "?referer=" + referer;
    }

    private User getUser(String accessToken) {
        return restTemplate.getForObject(gitlabServerUrl + "/api/v4/user?access_token=" + accessToken, User.class);
    }

    /**
     * 通过code去gitlab获取accessToken
     * @param code
     * @param redirectUri 回调地址，必须与授权时参数一致
     */
    private String getAccessToken(String code, String redirectUri) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "authorization_code");
        params.add("client_id", clientId);
        params.add("client_secret", clientSecret);
        params.add("code", code);
        params.add("redirect_uri", redirectUri);

        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);

        ResponseEntity<JSONAccessTokenResponse> response =
                restTemplate.exchange(gitlabServerUrl + "/oauth/token",
                        HttpMethod.POST,
                        entity,
                        JSONAccessTokenResponse.class);
        return Objects.requireNonNull(response.getBody()).access_token;
    }

    @Configuration
    class WebConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(
                    new HandlerInterceptorAdapter() {
                        @Override
                        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                            Optional<String> authorizationKeyOp = Arrays.stream(request.getCookies())
                                    .filter(it->it.getName().equals(AUTHORIZATION_KEY))
                                    .map(Cookie::getValue)
                                    .findAny();
                            if (authorizationKeyOp.isPresent()) {
                                // 授权信息存在，获取user信息放入session
                                RequestContextHolder.getRequestAttributes().setAttribute(CURRENT_USER, userStore.get(authorizationKeyOp.get()), RequestAttributes.SCOPE_SESSION);
                                return super.preHandle(request, response, handler);
                            } else {
                                // 授权信息不存在，去gitlab进行验证
                                String referer = request.getRequestURL().toString();
                                String redirectUri = URLEncoder.encode(buildCallbackUrl(referer), "utf-8");
                                String gitlabAuthUrl = gitlabServerUrl + "/oauth/authorize?response_type=code&redirect_uri=" + redirectUri + "&client_id=" + clientId;
                                logger.info("gitlabAuthUrl:{}", gitlabAuthUrl);
                                response.sendRedirect(gitlabAuthUrl);
                                return false;
                            }
                        }
                    })
                    .addPathPatterns("/main", "/test");
        }
    }

    static class JSONAccessTokenResponse implements Serializable {
        public String access_token;
    }

    static class User implements Serializable {
        public String name;
        public String username;
    }

}
